package com.vertx.test.vertx;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.vertx.test.vertx.handler.VertxHandler;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.json.jackson.DatabindCodec;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.CorsHandler;
import io.vertx.ext.web.handler.LoggerHandler;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.util.Map;


/**
 * 关于压测:
 * https://github.com/link1st/go-stress-testing
 */
@Configuration
@ConfigurationProperties(prefix = "vertx-config")
@Slf4j
@Data
public class VertxCore implements ApplicationListener<ApplicationReadyEvent> {

  private int port;
  private int bodyLimit;
  private boolean loggerEnable;

  private Vertx vertx;
  private Router router;

  //init vertx and router
  @PostConstruct
  private void init() {
    //The default number of threads in the internal blocking pool (used by some internal operations) = 20
    //eventLoopPoolSize: Number default is 2 times of the available processors
    //the worker pool-size: Number default : 20, here is 8 times of the available  processors--- undertow default cfg;
    int ioNumber = Math.max(Runtime.getRuntime().availableProcessors(), 2);
    VertxOptions vertxOptions = new VertxOptions();
    vertxOptions.setWorkerPoolSize(ioNumber * 8);
    this.vertx = Vertx.vertx(vertxOptions);
    this.router = Router.router(vertx);


    //////////////////////////////////////////////////////////////////////
    //logger,cors,body handler
    Route route = router.route();
    if (loggerEnable) {
      route.handler(LoggerHandler.create());
    }

    route.handler(CorsHandler.create()
        .addOrigin("*")
        .allowedMethod(HttpMethod.GET)
        .allowedMethod(HttpMethod.POST))
      .handler(BodyHandler.create().setBodyLimit(bodyLimit));

    //jackson mapper config setting
    DatabindCodec.mapper().setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
    DatabindCodec.prettyMapper().setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
  }


  /**
   * Handle an application event.
   *
   * @param event the event to respond to
   */
  @Override
  public void onApplicationEvent(ApplicationReadyEvent event) {
    ConfigurableApplicationContext applicationContext = event.getApplicationContext();

    //register handler into route
    Map<String, VertxHandler> beansOfType = applicationContext.getBeansOfType(VertxHandler.class);
    beansOfType.values().forEach(VertxHandler::registerRoute);
    beansOfType.clear();

    //start vertx server
    HttpServerOptions httpServerOptions = new HttpServerOptions();
    if (loggerEnable) {
      httpServerOptions.setLogActivity(true);
    }
    httpServerOptions
      .setCompressionSupported(true);
    HttpServer httpServer =
      vertx.createHttpServer(httpServerOptions);
    httpServer.requestHandler(router);
    httpServer.listen(this.port, listenHandler -> {
      if (listenHandler.succeeded()) {
        log.info("Server started ok , listen port:{}", port);
      }
    });
  }


  //get
  public void get(String path, Handler<RoutingContext> requestHandler) {
    this.addHandler(HttpMethod.GET, path, requestHandler);
  }

  //post
  public void post(String path, Handler<RoutingContext> requestHandler) {
    this.addHandler(HttpMethod.POST, path, requestHandler);
  }


  //add handler
  private void addHandler(HttpMethod method, String path, Handler<RoutingContext> requestHandler) {
    synchronized (this) {
      if (this.router == null) {
        log.info("warning, router not init  done");
        return;
      }
      //event-io / worker-io-block
      this.router.route(method, path).blockingHandler(requestHandler, false);
    }
  }

}
